#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <wlr/backend/interface.h>
#include <wlr/util/log.h>
#include "backend/sylixosinput.h"
#include "util/signal.h"

static struct wlr_sylixosinput_backend *get_sylixosinput_backend_from_backend(
		struct wlr_backend *wlr_backend) {
	assert(wlr_backend_is_sylixosinput(wlr_backend));
	return (struct wlr_sylixosinput_backend *)wlr_backend;
}

static int handle_xmse_readable(int fd, uint32_t mask, void *_backend) {
	struct wlr_sylixosinput_backend *backend = _backend;
    handle_xmse_event(backend, backend->xmse_fd);
	return 0;
}

static int handle_xkbd_readable(int fd, uint32_t mask, void *_backend) {
    struct wlr_sylixosinput_backend *backend = _backend;
    handle_xkbd_event(backend, backend->xkbd_fd);
    return 0;
}

static bool backend_start(struct wlr_backend *wlr_backend) {
	struct wlr_sylixosinput_backend *backend =
		get_sylixosinput_backend_from_backend(wlr_backend);
	wlr_log(WLR_DEBUG, "Starting sylixosinput backend");
    if (backend->xmse_fd <= 0) {
        backend->xmse_fd = open("/dev/input/xmse", O_RDONLY, 0777);
        if (backend->xmse_fd < 0) {
            wlr_log(WLR_ERROR, "Failed to open xmse device");
            return false;
        }
    }

    if (backend->xkbd_fd <= 0) {
        backend->xkbd_fd = open("/dev/input/xkbd", O_RDONLY, 0777);
        if (backend->xkbd_fd < 0) {
            wlr_log(WLR_ERROR, "Failed to open xkbd device");
            close(backend->xmse_fd);
            return false;
        }
    }

    handle_device_added(backend);

	struct wl_event_loop *event_loop =
		wl_display_get_event_loop(backend->display);
	if (backend->xmse_event) {
		wl_event_source_remove(backend->xmse_event);
	}
    if (backend->xkbd_event) {
        wl_event_source_remove(backend->xkbd_event);
    }

	backend->xmse_event = wl_event_loop_add_fd(event_loop, backend->xmse_fd,
			WL_EVENT_READABLE, handle_xmse_readable, backend);
	if (!backend->xmse_event) {
		wlr_log(WLR_ERROR, "Failed to create xmse event on event loop");
		return false;
	}

    backend->xkbd_event = wl_event_loop_add_fd(event_loop, backend->xkbd_fd,
            WL_EVENT_READABLE, handle_xkbd_readable, backend);
    if (!backend->xkbd_event) {
        wlr_log(WLR_ERROR, "Failed to create xkbd event on event loop");
        return false;
    }

#if defined(SYLIXOSINPUT_DEBUG)
	// force show the cursor
	mouse_event_notify event;
	event.ctype = MOUSE_CTYPE_REL;
	event.ctype = MOUSE_CTYPE_REL;
	event.xmovement =  1;
	event.ymovement =  1;
	struct wlr_sylixosinput_input_device *dev1 = backend->device;
	handle_pointer_motion(&event, &dev1->pointer);
#endif

	wlr_log(WLR_DEBUG, "sylixosinput successfully initialized");
	return true;
}

static void backend_destroy(struct wlr_backend *wlr_backend) {
	if (!wlr_backend) {
		return;
	}
	struct wlr_sylixosinput_backend *backend =
		get_sylixosinput_backend_from_backend(wlr_backend);

	struct wlr_sylixosinput_input_device *dev, *tmp;
	wl_list_for_each_safe(dev, tmp, &backend->devices, link) {
		destroy_sylixosinput_input_device(dev);
	}

	wlr_backend_finish(wlr_backend);

	wl_list_remove(&backend->display_destroy.link);
	wl_list_remove(&backend->session_destroy.link);
	wl_list_remove(&backend->session_signal.link);

	if (backend->xmse_event) {
		wl_event_source_remove(backend->xmse_event);
	}

    if (backend->xkbd_event) {
        wl_event_source_remove(backend->xkbd_event);
    }

	free(backend);
}

static const struct wlr_backend_impl backend_impl = {
	.start = backend_start,
	.destroy = backend_destroy,
};

bool wlr_backend_is_sylixosinput(struct wlr_backend *b) {
	return b->impl == &backend_impl;
}

static void handle_display_destroy(struct wl_listener *listener, void *data) {
	struct wlr_sylixosinput_backend *backend =
		wl_container_of(listener, backend, display_destroy);
	backend_destroy(&backend->backend);
}

struct wlr_backend *wlr_sylixosinput_backend_create(struct wl_display *display) {
	struct wlr_sylixosinput_backend *backend =
		calloc(1, sizeof(struct wlr_sylixosinput_backend));
	if (!backend) {
		wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno));
		return NULL;
	}
	wlr_backend_init(&backend->backend, &backend_impl);

	wl_list_init(&backend->devices);

	backend->display = display;
	backend->display_destroy.notify = handle_display_destroy;
	wl_display_add_destroy_listener(display, &backend->display_destroy);

	return &backend->backend;
}

uint32_t usec_to_msec(uint64_t usec) {
	return (uint32_t)(usec / 1000);
}
